/*
 * Copyright 2012 Alain Pannetrat (L1L1@gmx.com)
 * 
 * This file is part of cookie-miner.
 * 
 * cookie-miner is free software: you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by 
 * the Free Software Foundation, either version 3 of the License, or 
 * (at your option) any later version.
 * 
 * cookie-miner is distributed in the hope that it will be useful, but 
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License 
 * along with cookie-miner. If not, see http://www.gnu.org/licenses/.
 *
 * @version: $Id$
 *
 */
#include "tcp.h"
#include <cstring>
#include <string>
#ifdef _WIN32
#include <winsock.h>
#define socklen_t int
#else
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif
#include <cerrno>
#include <cstdlib>
#include "log_facility.h"

/**********************************************************
 * SocketAddress
 */

bool SocketAddress::ip(const char *ip_addr)
{
	unsigned i;
	// TODO: more serious testing should be done here
	for (i=0;i<39;i++)
	{
		if (ip_addr[i]==':' || ip_addr[i]==0)
			break;
		_ip_addr[i]=ip_addr[i];
	}
	_ip_addr[i]=0;
	return i>0;		
}

bool SocketAddress::set(const char *ip_port)
{
	if (!set(ip_port)) return false;
	ip_port = ip_port+strlen(_ip_addr);
	if (*ip_port!=':') return false;
	ip_port++;
	_port=atol(ip_port);
	return true;
}

/**********************************************************
 * TCP functions
 */


#ifdef _WIN32
int inet_aton(const char *ip_str, struct in_addr *ip)
{
	ip->s_addr=inet_addr(ip_str);
	return 1;
}
#endif

int is_ipv4_address(const char *addr)
{
	while ((*addr>='0' && *addr<='9') || *addr=='.') addr++;
	return (*addr==0);
}

int tcp_connect(const char *hoststring)
{
	int sock;
	struct hostent *he;
	struct sockaddr_in addr;
	struct in_addr ip;
	char host[256];
	unsigned port=80;
	int len;
	const char *sep;

	if ((sep=strchr(hoststring,':'))!=NULL)
	{
		len = sep-hoststring;

		if (len>255) len=255;
		memcpy(host,hoststring,len);
		host[len]=0;
		port = atoi(sep+1);
	}
	else
	{
		len = strlen(hoststring);
		if (len>255) len=255;
		memcpy(host,hoststring,len);
		host[len]=0;
	}

	memset(&addr,0,sizeof(addr));
	if (is_ipv4_address(host)) /* assume dotted ip addr */
	{
		if (!inet_aton(host,&ip)) {
			logger.message(logger.LOG_ERROR,"inet_aton() failed on '%s'",host);
			return -1;
		}
		addr.sin_addr = ip;
	}
	else /* hostname */
	{
		he = gethostbyname(host);
		if (he==NULL) {
			logger.message(logger.LOG_ERROR,"gethostbyname() failed on '%s'",host);
			return -1;
		}
		addr.sin_addr   = *(struct in_addr*)he->h_addr_list[0];
	}
	addr.sin_family = AF_INET;
	addr.sin_port   = htons(port);

	sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
	if (sock<0) {
		logger.message(logger.LOG_ERROR,"socket() failed on destination '%s:%u', %s",host,port,strerror(errno));
		return sock;
	}
	if (connect(sock,(struct sockaddr *)&addr,sizeof(addr))<0) {
		logger.message(logger.LOG_ERROR,"connect() failed on destination '%s:%u', %s",host,port,strerror(errno));
		return -1;
	}
	return sock;
}	

int tcp_listen(unsigned port)
{
	int sock;
	struct sockaddr_in sin;
	int optval = 1;
	
	sock = socket(AF_INET,SOCK_STREAM,0);
	if (sock<0) {
		logger.message(logger.LOG_ERROR,"socket() failed for proxy, %s",strerror(errno));
		return sock;
	}
	memset(&sin,0,sizeof(sin));
	sin.sin_addr.s_addr = INADDR_ANY;
	sin.sin_family = AF_INET;
	sin.sin_port = htons(port);
	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&optval, sizeof(optval));
	if (bind(sock,(struct sockaddr *)&sin, sizeof(sin))<0) {
		logger.message(logger.LOG_ERROR,"bind() failed for proxy, %s",strerror(errno));
		return -1;
	}
	listen(sock,5);
	return sock;
}

int tcp_accept(int sock, char *caller)
{
	int s;
	socklen_t alen;
	struct sockaddr_in sin;

	alen = sizeof(sin);
	s = accept(sock,(struct sockaddr *)&sin,&alen);
	if (caller)
		sprintf(caller,"%s:%u",inet_ntoa(sin.sin_addr),ntohs(sin.sin_port));
	return s;
}

SocketAddress socket_get_local_address(int sock)
{
    struct sockaddr_in sin;
    socklen_t sin_len = sizeof(sin);
    SocketAddress sa;

    if (getsockname(sock,(struct sockaddr *)&sin,&sin_len)==0)
    {
	    sa.ip(inet_ntoa(sin.sin_addr));
	    sa.port(ntohs(sin.sin_port));
    }
    return sa;
}

SocketAddress socket_get_peer_address(int sock)
{
    struct sockaddr_in sin;
    socklen_t sin_len = sizeof(sin);
    SocketAddress sa;

    if (getpeername(sock,(struct sockaddr *)&sin,&sin_len)==0)
    {
	    sa.ip(inet_ntoa(sin.sin_addr));
	    sa.port(ntohs(sin.sin_port));
    }
    return sa;
}

void  tcp_close(int sock)
{
#ifdef _WIN32
	shutdown(sock,0);
	closesocket(sock);
#else
	shutdown(sock,SHUT_RDWR);
	close(sock); 
#endif
}

/********************************************************************
 *
 *	PRELOAD WINDOWS STUFF
 *
 */


#ifdef _WIN32
class UseSockets {
	public:
		UseSockets();
		~UseSockets();
	private:
		static bool loaded;
};

UseSockets USE_SOCKETS;

bool UseSockets::loaded = false;

UseSockets::UseSockets()
{
    if (loaded) return;
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData)!=0) 
	logger.message(logger.LOG_ERROR,"WSAStartup failed\n");
    else
	loaded = true;
}

UseSockets::~UseSockets()
{
    if (!loaded) return;
    WSACleanup();
}
#endif

